    
package CheckTree;

import Grafica.Vertice;
import java.awt.Point;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.*;
import javax.swing.tree.TreePath;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.TreeNode;
//import  static pantera.GraphViz.*;



public class CheckTreeManager extends MouseAdapter implements
        TreeSelectionListener, java.io.Serializable
{
    private static final long serialVersionUID = -8300922489947665565L;
    
    private CheckTreeSelectionModel selectionModel;
    private TreePathSelectable selectable;
    String colorV = "black";
    protected JTree tree = new JTree();
    protected TreeNode raiz;
    int hotspot = new JCheckBox().getPreferredSize().width;
    private StringBuilder G;
    String contVert;

    private class EscuchaArbol implements TreeModelListener {
    
    /**Se actualiza el peso del nodo n.
     * 
     * @param n El nodo a actualizar
     * @param peso El peso que se le pondrá
     */
    public void actualizaPeso(DefaultMutableTreeNode n, String peso){
        String vert = ""+n.getUserObject();
        vert = vert.indexOf(" ") != -1 ? vert.substring(0, vert.indexOf(" "))
                : vert;
        n.setUserObject(vert+" - "+peso);
    }

    /**Actualiza los pesos de todos los nodos contenidos en hijos
     * con el valor <code>peso</code>
     *
     * @param hijos Los nodos a actualizar
     * @param peso  El peso con el que se va a actualizar
     */
    public void actualizaPesos(Enumeration<DefaultMutableTreeNode> hijos,
            String peso){
        while(hijos.hasMoreElements())
            actualizaPeso(hijos.nextElement(),peso);
    }



    /**Actualiza el peso del hijo de n con número de vértice j
     *
     * @param n Un nodo del árbol
     * @param j El número asociado al vértice que se desea actualizar
     * @param peso La cadena con el peso
     */
    public void actualizaPeso(DefaultMutableTreeNode n, int nV, String peso){
        int nVertP = getNv(n);
        if(nVertP>nV){
          nVertP--;
        }
        actualizaPeso((DefaultMutableTreeNode)
                     (raiz.getChildAt(nV).getChildAt(nVertP)),peso);
    }

    public void treeNodesChanged(TreeModelEvent e) {

        DefaultMutableTreeNode node;
        node = (DefaultMutableTreeNode)
                 (e.getTreePath().getLastPathComponent());
        /*
         * If the event lists children, then the changed
         * node is the child of the node we've already
         * gotten.  Otherwise, the changed node and the
         * specified node are the same.
         */
        try {
            int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode)
                   (node.getChildAt(index));            
        } catch (NullPointerException exc) {
            System.out.println("Kabooom!");
        }
           
            try{
                Double.parseDouble(node.getUserObject().toString());
                String peso = node.getUserObject().toString();
                if(node.isLeaf()){
                    node.setUserObject("V"+getNv(contVert)+" - "+peso);
                    actualizaPeso((DefaultMutableTreeNode)node.getParent(),
                            getNv(contVert), peso);
                }else{
                    node.setUserObject(contVert);
                    Enumeration<DefaultMutableTreeNode> hijos = 
                            (Enumeration<DefaultMutableTreeNode>)
                            node.children();
                    if(!node.isRoot()){
                        actualizaPesos(hijos,peso);
                        hijos = (Enumeration<DefaultMutableTreeNode>)node
                                .children();
                        while(hijos.hasMoreElements())
                            actualizaPeso(node,getNv(
                                    (DefaultMutableTreeNode)
                                    hijos.nextElement()), peso);
                    }else
                        while(hijos.hasMoreElements())
                            actualizaPesos(hijos.nextElement().children(),peso);
                }
            }catch(NumberFormatException es){

                JOptionPane.showMessageDialog(null,
                        "El peso de las aristas debe ser un número");
                node.setUserObject(contVert);
            }
        tree.updateUI();
        System.out.println("The user has finished editing the node.");
        System.out.println("New value: " + node.getUserObject());
    }
    public void treeNodesInserted(TreeModelEvent e) {
    }
    public void treeNodesRemoved(TreeModelEvent e) {
    }
    public void treeStructureChanged(TreeModelEvent e) {
    }
 }

    /**Nos regresa el peso de la arista vi, padre de vi
     *
     * @param i Un nodo a profundidad dos en el árbol
     * @return El peso de la arista
     */
    private int getPeso(DefaultMutableTreeNode i){
        String p = ((String)i.getUserObject());
        int j = p.lastIndexOf(" ");
        if(j!=-1)
            return Integer.parseInt(p.substring(j+1));
        
        return 1 + (int)((Math.random()*10)+1);
    }

    /**Nos regresa un arreglo con los pesos de las aristas
     *
     * @return Un arreglo con los pesos de las aristas
     */
   public int[][] getPesos(){
       if(raiz!=null){
        int[][]pesos = new int[raiz.getChildCount()][];
        for(int i=raiz.getChildCount()-1;i>=0;i--)
                pesos[i] = new int[i+1];

        Enumeration<DefaultMutableTreeNode> nod = ((DefaultMutableTreeNode)
                   raiz).children();

        while(nod.hasMoreElements()){
            DefaultMutableTreeNode u=nod.nextElement();
            Enumeration<DefaultMutableTreeNode> tmp = u.children();
            while(tmp.hasMoreElements()){
                DefaultMutableTreeNode v = tmp.nextElement();
                int i = getNv((String)u.getUserObject());
                int j = getNv((String)v.getUserObject());
                if(i<j)
                       pesos[j][i] = getPeso(v);
                else
                       continue;
               }
        }
        return pesos;
       }else
           return null;
   }

   public CheckTreeManager(JTree tree){
      
        this.tree = tree;
        DefaultTreeCellEditor ed = (DefaultTreeCellEditor)tree.getCellEditor();

        tree.getModel().addTreeModelListener(new EscuchaArbol());
        selectionModel = new CheckTreeSelectionModel(tree.getModel());
        tree.setCellRenderer(new CheckTreeCellRenderer(tree.getCellRenderer(),
                selectionModel));
        tree.addMouseListener(this);
        selectionModel.addTreeSelectionListener(this);        
    }



   public CheckTreeManager(JTree tree,boolean dig,TreePathSelectable selectable)
   {

        this.tree = tree;
        selectionModel = new CheckTreeSelectionModel(tree.getModel(), dig);
        this.selectable = selectable;

        // note: if largemodel is not set
        // then treenodes are getting truncated.
        // need to debug further to find the problem
        if (selectable != null)
            tree.setLargeModel(true);

        tree.setCellRenderer(new CheckTreeCellRenderer(tree.getCellRenderer(),
                selectionModel, selectable));
        tree.addMouseListener(this);
        selectionModel.addTreeSelectionListener(this);
    }

    public JTree getArbol(){
        return tree;
    }

    /**Regresa el número de vértice asociado al contenido del Nodo
     *
     * @param nV Una cadena con el contenido de algún nodo
     * @return  Un int con el número del vértice
     */
    private int getNv(String nV){
        int trmna = nV.indexOf(" ");
        String num = nV.substring(1, trmna!=-1 ? trmna : nV.length());
        return num.indexOf("istas")!=-1 ? -1 : Integer.parseInt(num);
    }

    /**Regresa el número de vértice asociado al Nodo
     *
     * @param n El nodo
     * @return  Un int con el número del vértice
     */
    public int getNv(DefaultMutableTreeNode n){
        return getNv((String)n.getUserObject());
    }

    /**Genera una arista en el lenguje de Graphviz a partir de una TreePath
     *
     * @param tp La trayectoria de la que se genera la arista
     * @return Una cadena que representa a una arista en el lenguaje Graphviz
     */
   /*private String generaArista(TreePath tp){
    if(tp!=null){
     DefaultMutableTreeNode v1 = (DefaultMutableTreeNode)tp.getPathComponent(1);
     DefaultMutableTreeNode v2 = (DefaultMutableTreeNode)tp.getPathComponent(2);
     //   if(getNv(v1)<getNv(v2))
            return generaArista(getNv(v1),getNv(v2));
        }
        return "";
    }*/

    /**Regresa todas las aristas en el lenguaje de Graphviz
     * 
     * @return String aristas en el lenguaje de Graphviz
     */
    public String aristasGV(){
       TreePath checkedPaths[] = getSelectionModel().getSelectionPaths();
       G = new StringBuilder();

       if(checkedPaths!=null){
       Hashtable tP = new Hashtable();
       for(TreePath tp : checkedPaths){
           int nV = tp.getPathCount();

           switch(nV){
               case 2:
               aristasDeV(getNv((DefaultMutableTreeNode)
                       (tp.getLastPathComponent())), raiz.getChildCount());
               break;
               case 3:
                   generaA(tp);
                   break;
               default:
                   agregaTodos(raiz.getChildCount());
                   break;
           }
       }
       }
       return  G.toString();
    }

     /**Genera la arista (x,y) en el lenguaje de Graphviz
     *
     * @param x El índice de algún vértice
     * @param y El índice de algún vértice
     * @return La arista en forma de cadena
     */
    public String generaArista(int x, int y){

        String arista = "V"+ (x<y ? x : y) + "->" + "V" + (x<y ? y : x) + "[arrowhead=none];\n";
        //String arista0 = "V"+y + "->" + "V" + x + "[arrowhead=none];\n";

        //return x<y && b.indexOf(arista.substring(0,arista.length()-1))==-1 ?
        //G.indexOf(arista0)==-1 && x!=y ?
        String a = G.indexOf(arista)==-1 && x!=y ?
            arista : "";
        G.append(a);
        return a;
    }

     /**Genera una cadena en el lenguaje de graphviz con todas las aristas
     * que pueden salir del vértice número v.
     *
     * @param v Un número de algún vértice
     * @param nV El número de vértices de la gráfica
     * @return Una cadena en el lenguaje de Graphviz
     */
    public String aristasDeV(int v, int nV){
        for(int j=0;j<nV;j++)
               generaArista(v,j);
        return G.toString();
    }

    /**Regresa todas las aristas de una gráfica completa con la cantidad de vér-
     * tices actuales.
     * @param nV El número de vértices de la gráfica
     * @return String una cadena en el lenguaje de Graphviz
     */
    public String agregaTodos(int nV){        
        for(int i=0;i<nV;i++)
            aristasDeV(i,nV);
        return G.toString();
    }

    /**Nos regresa un arreglo de <code>Vertice</code>
     * de acuerdo a la gráfica actual
     * @return <code>Vertice[]</code>
     */
    public Vertice[] getVertices(){
        if(raiz==null)
            return null;
        Vertice[]vertices = new Vertice[raiz.getChildCount()];
        for(int i=0;i<vertices.length;i++)
            vertices[i] = new Vertice("V"+i);
        
        TreePath checkedPaths[] = getSelectionModel().getSelectionPaths();
        if(checkedPaths!=null)
        for(TreePath tp : checkedPaths){
          switch(tp.getPathCount()){
           case 2:
            int nV = getNv((DefaultMutableTreeNode)(tp.getLastPathComponent()));
            agregaTodosAv(nV,vertices);
            break;

           case 3:               
            int u = getNv((DefaultMutableTreeNode)(tp.getLastPathComponent()));
            int v = getNv((DefaultMutableTreeNode)(tp.getPathComponent(1)));
            agregaArista(vertices[u],vertices[v]);
            break;
           case 1:
            for(int i=0;i<raiz.getChildCount();i++)
              agregaTodosAv(i,vertices);
            break;
          }
       }
        return vertices;
    }

    /**Agrega todas las aristas (v,vi) para todo vi en <code>vertices</code>
     *
     * @param v
     */
    private void agregaTodosAv(int v,Vertice[]vertices){
        for(int j=0;j<raiz.getChildCount();j++)
                agregaArista(vertices[v],vertices[j]);
    }

    /**Agrega u a la lista de adyacencias de v y v a la lista de adyacencias de
     * u
     *
     * @param v 
     * @param u
     */
    private void agregaArista(Vertice v, Vertice u){
        if(!v.esAdyacente(u) && v.nV() != u.nV()){
           v.agrega(u);
           u.agrega(v);
        }
    }


    /* Regresa una cadena que representa la gráfica en el lenguaje de GraphViz
     * @returns String
     **/
    public String generaGrafica(){
       if(raiz!=null){
        StringBuilder grafica = new StringBuilder("digraph G {\n");
       
        for(int i=0;i<raiz.getChildCount();i++)
            grafica.append("V"+i+" [shape=ellipse, width=.5, height=.5];\n");

        grafica.append(aristasGV());
        grafica.append("}");
        return grafica.toString();
       }else
           return null;
    }

    /**Genera una arista en el lenguje de Graphviz a partir de una TreePath
     *
     * @param tp La trayectoria de la que se genera la arista
     * @return Una cadena que representa a una arista en el lenguaje Graphviz
     */
   private String generaA(TreePath tp){
    if(tp!=null){
     DefaultMutableTreeNode v1 = (DefaultMutableTreeNode)tp.getPathComponent(1);
     DefaultMutableTreeNode v2 = (DefaultMutableTreeNode)tp.getPathComponent(2);
     return generaArista(getNv(v1),getNv(v2));
    }
        return "";
   }

     /* Regresa una cadena que representa la gráfica en el lenguaje de GraphViz,
      * que contiene marcados los vértices con grado impar en rojo
     * @returns String
     **/
    public String generaGrafica(int[]grados){
        StringBuffer grafica = new StringBuffer("digraph G {\n");
        
        grafica.append("center = true;\n");
        for(int i=0;i<raiz.getChildCount();i++)
            grafica.append("V"+i+" ["+(grados[i]%2==1 ? 
                "color=red,style=filled,":"")+
                "shape=ellipse, width=.5, height=.5];\n");
        
       grafica.append(aristasGV());

        grafica.append("}");
        return grafica.toString();
    }

    /**Marca el vértice con índice v de color c
     *
     * @param v El indice del vértice
     * @param color Un color usable para Graphviz
     * @return
     */
    public String generaGrafica(int v, String c){
        colorV = c;
        StringBuffer grafica = new StringBuffer("digraph G {\n");
        
        grafica.append("center = true;\n");
        for(int i=0;i<raiz.getChildCount();i++){
            grafica.append("V"+i+" ["+(v==i ? "color=\""+colorV+
                    "\",style=filled,":"")+
                    "shape=ellipse, width=.5, height=.5];\n");
        }

        grafica.append(aristasGV());

        grafica.append("}");
        return grafica.toString();
    }

    public TreePathSelectable getSelectable(TreePathSelectable selectable)
    {
        return selectable;
    }

    /**Marca la trayectoria <code>raiz, i, j</code>
     * que representa a la arista (vi,vj).
     *
     * @param i 
     * @param j
     */
    public void agregaTrayectoria(int i, int j, DefaultMutableTreeNode raiz){
        this.raiz = raiz;
       if(i<j){
            TreeNode[]nod = {raiz,raiz.getChildAt(j),
                    raiz.getChildAt(j).getChildAt(i)};
            selectionModel.addSelectionPath(new TreePath(nod));
            j = j-1;
            TreeNode[]nodos = {raiz,raiz.getChildAt(i),
            raiz.getChildAt(i).getChildAt(j)};
            selectionModel.addSelectionPath(new TreePath(nodos));
        }

    }

    /**Se encarga de modificar las listas de adyacencias,
     * si <code>seleccionado</code> la <code>Treepath t</code> se agrega
     * a la lista de adyacencias, si no, se quita.
     *
     * @param t La trayectoria que fue seleccionada
     * @param seleccionado Un boolean que nos dice si la trayectoria está
     * marcada
     */
    public void modificaLista(TreePath t, boolean seleccionado){   
        
        TreeNode tn = null;
        Enumeration vecinos = null;
        int i,j;
        TreePath pa;
        raiz = (TreeNode)t.getPath()[0];

        switch(t.getPathCount()){
            case 3:
                TreeNode v1 = (TreeNode)(t.getPathComponent(1));
                TreeNode v2 = (TreeNode)(t.getLastPathComponent());
                
                i = getNv((DefaultMutableTreeNode)v1);
                j = getNv((DefaultMutableTreeNode)v2);
                i = i<j ? i : i-1;
                TreeNode[]nodos = {raiz,raiz.getChildAt(j),
                raiz.getChildAt(j).getChildAt(i)};
                pa = new TreePath(nodos);
                if(seleccionado){
                    selectionModel.removeSelectionPath(pa);
                    selectionModel.removeSelectionPath(t);
                }else{
                    selectionModel.addSelectionPath(pa);
                    selectionModel.addSelectionPath(t);
                }
                
                break;

            case 2:
                tn = (TreeNode)(t.getPathComponent(1));
                vecinos = tn.children();
                while(vecinos.hasMoreElements())
                    modificaLista(t.pathByAddingChild(vecinos.nextElement()),
                                   seleccionado);
                
                break;

            case 1:
                for(int u=0;u<raiz.getChildCount();u++)
                    modificaLista(t.pathByAddingChild(raiz.getChildAt(u)),
                                  seleccionado);
                
                break;

        }
    }

    @Override
    public void mouseClicked(MouseEvent me)
    {
        TreePath path = tree.getPathForLocation(me.getX(), me.getY());       

        if (path == null)
            return;

         contVert = (String)
                   (((DefaultMutableTreeNode)path.getLastPathComponent())
                   .getUserObject());

        if (me.getX() > tree.getPathBounds(path).x + hotspot)
            return;

        if (selectable != null && !selectable.isSelectable(path))
            return;

        boolean selected = selectionModel.isPathSelected(path,
                selectionModel.isDigged());
        selectionModel.removeTreeSelectionListener(this);
        try
        {            
            modificaLista(path,selected);                   
        }
        finally
        {
            selectionModel.addTreeSelectionListener(this);
            tree.treeDidChange();
        }
    }

    public CheckTreeSelectionModel getSelectionModel()
    {
        return selectionModel;
    }


    public void valueChanged(TreeSelectionEvent e)
    {
        tree.treeDidChange();
    }
}